﻿using com.ytocargo.Application.BackgroundJobInfos;
using com.ytocargo.Core.BackgroundJobInfos;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Yto.Common.Dependency;
using Yto.Common.Extensions;
using Yto.Threading;
using Yto.Threading.BackgroundWorkers;

namespace com.ytocargo.Application.BackgroundJobs
{
    public class BackgroundJobManager : TimedBgdWorker, IBackgroundJobManager, ISingleton
    {
        private readonly IBackgroundJobInfoServices _services;
        private readonly ICtnResolver _ctnResolver;

        public BackgroundJobManager(BaseTimer timer,
            ICtnResolver ctnResolver,
            IBackgroundJobInfoServices services)
            : base(timer)
        {
            baseTimer.Period = JobPeriod;
            _ctnResolver = ctnResolver;

            _services = services;
        }

        public int JobPeriod { get; } = 5000;

        public async Task EnqueueAsync<T, TArgs>(TArgs args, DateTime? nextTryTime = null,
            BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? period = null) where T : IBackgroundJob<TArgs>
        {
            var jobInfo = new BackgroundJobInfo()
            {
                JobType = typeof(T).AssemblyQualifiedName,
                JobArgs = args.ToJsonString(),
                Priority = priority
            };
            if (nextTryTime.HasValue)
            {
                jobInfo.NextTryTime = nextTryTime.Value;
            }
            else
            {
                jobInfo.NextTryTime = DateTime.Now;
            }

            await _services.InsertAsync(jobInfo);
        }

        protected override void DoWork()
        {
            var waitJobInfo = AsynchronizeHelper.RunSync(() => _services.GetWaitingJobsAsync(5));

            foreach (var item in waitJobInfo)
            {
                ProcessJobInfo(item);
            }
        }


        private void ProcessJobInfo(BackgroundJobInfo jobInfo)
        {
            try
            {
                jobInfo.TryCount++;
                jobInfo.LastTryTime = DateTime.Now;

                var jobType = Type.GetType(jobInfo.JobType);
                using (var job = _ctnResolver.ResolverWithDispose(jobType))
                {
                    try
                    {
                        var executeMethod = job.WapperObj.GetType().GetMethod("Execute");
                        var argsType = executeMethod.GetParameters()[0].ParameterType;
                        var args = JsonConvert.DeserializeObject(jobInfo.JobArgs, argsType);

                        executeMethod.Invoke(job.WapperObj, new[] { args });

                        AsynchronizeHelper.RunSync(() => _services.DeleteAsync(jobInfo));
                    }
                    catch (Exception ex)
                    {
                        Logger.Warn(ex.Message, ex);
                        var nextTrytime = jobInfo.CaculateNextTry();
                        if (nextTrytime.HasValue)
                        {
                            jobInfo.NextTryTime = nextTrytime.Value;
                        }
                        else
                        {
                            jobInfo.IsAbandoned = true;
                        }

                        _services.UpdateAsync(jobInfo);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Warn(ex.Message, ex);
                jobInfo.IsAbandoned = true;
                _services.UpdateAsync(jobInfo);
            }
        }

    }
}
